Módulo 7 · Guia Interativo · 2026

RAG — Retrieval-Augmented Generation

O clímax do curso! Tudo que você aprendeu até aqui se conecta neste pipeline: parsing, chunking, embeddings, busca vetorial, reranking, prompts e deploy. Vamos construir um RAG completo para a Nimbus Cloud.

🧹 Parsing/ETL ✂️ Chunking 🔢 Embeddings 🗄️ Vector DB 🔍 Busca Híbrida 🎯 Reranking 📝 Prompts 🚀 Deploy

1 O que é RAG?

A arquitetura que conecta LLMs aos seus documentos em tempo real.

💡 Analogia
📚RAG é como dar ao seu estagiário genial uma pilha de documentos antes de fazer a pergunta. Ele não responde só com o que sabe — responde com o que encontrou nos documentos.
Um LLM sozinho é como um engenheiro sênior que estudou muito, mas nunca viu seus arquivos específicos. O RAG resolve isso: antes de responder, o sistema busca os documentos mais relevantes e os entrega ao modelo como contexto. O modelo então responde fundamentado — não no que "acha".

Por que RAG é necessário?

❌ Sem RAG

Usuário: "O ticket #4521 foi resolvido?"

LLM: "Não tenho acesso a tickets específicos..."

Ou pior: inventa um número de ticket.

✅ Com RAG

Usuário: "O ticket #4521 foi resolvido?"

Sistema: busca ticket #4521 → passa ao LLM

LLM: "Sim. Ticket #4521 da Acme Corp foi resolvido em 21/06/2026 aplicando NIMBUS_TIMEOUT=120."

Cenário: Nimbus Cloud

Vamos construir um RAG para a Nimbus Cloud, empresa fictícia de software SaaS. Ela tem:

🎫

Tickets de suporte

Milhares de tickets resolvidos, com erros, logs e soluções.

📘

Documentação da API

Guias, endpoints, parâmetros, changelogs de versão.

💬

FAQ interno

Perguntas frequentes da equipe de Customer Success.

📝

Runbooks

Procedimentos de incidentes e post-mortems.

💡 Conexão com módulos anteriores: RAG usa tudo que aprendemos: ML clássico (Módulo 1) para clustering, redes neurais (Módulo 2) para embeddings, NLP (Módulo 3) para tokenização, Transformers (Módulo 4) para modelos, LLMs (Módulo 5) para geração, e Prompt Engineering (Módulo 6) para prompts robustos.

2 O pipeline completo

Duas fases independentes: indexação (offline) e consulta (online).

💡 Analogia
🏭RAG é como uma fábrica de respostas. De um lado, você alimenta com documentos (matéria-prima). Do outro, saem respostas fundamentadas. No meio, 5 estações de trabalho.

Fase 1 — Indexação (roda uma vez por documento)

📄Documentos
🧹Parsing/ETL
✂️Chunking
🔢Embedding
🗄️Vector DB

Fase 2 — Consulta (roda a cada pergunta)

Pergunta
🔢Embedding
🔍Busca Top-K
🎯Rerank
📝Prompt
Resposta

Exemplo real: pergunta sobre timeout

# Pergunta do usuário
"Por que minha sincronização falha após 30 segundos?"

# 1. Embedding da pergunta
→ [0.19, -0.68, 0.51, ...]  # 768 dimensões

# 2. Busca vetorial retorna top-5 chunks
1. Ticket #4521 (score 0.94) # "timeout 30s endpoint /v2/sync"
2. Docs API v3.2 (score 0.88) # "timeout default: 30s"
3. Runbook RB-018 (score 0.81) # "aumentar NIMBUS_TIMEOUT"

# 3. Prompt montado
SYSTEM: Responda com base no contexto. Cite a fonte.
CONTEXTO: [Ticket #4521] ... [Docs API] ... [Runbook]
PERGUNTA: Por que minha sincronização falha após 30 segundos?

# 4. LLM responde"O timeout padrão do endpoint /v2/sync é 30s (Docs API v3.2).
   Para arquivos >500MB, aumente a variável NIMBUS_TIMEOUT
   para 120s — solução aplicada no ticket #4521."

3 Parsing/ETL — limpando a sujeira

Antes do chunking, vem a parte mais subestimada: transformar documentos brutos em texto limpo.

💡 Analogia
🧺Parsing é como lavar e passar uma camisa antes de vestir. A camisa (documento) até serve, mas com amassados e manchas fica difícil de usar.

Os 4 vilões do parsing

📑

PDFs escaneados

Sem camada de texto — precisa de OCR (Tesseract, pdfplumber).

📊

Tabelas em PDF

Linhas viram texto embaralhado. Use Camelot, Tabula ou LlamaParse.

🔁

Cabeçalhos/rodapés

Repetem em cada página e poluem os chunks. Precisam ser removidos.

🌐

HTML de wikis

Tags, menus e footers precisam ser extraídos (BeautifulSoup, Trafilatura).

Exemplo: extraindo um ticket do Zendesk

import requests
from unstructured.partition.html import partition_html

def fetch_ticket(ticket_id):
    # 1. Busca ticket via API
    r = requests.get(f"https://nimbus.zendesk.com/api/v2/tickets/{ticket_id}.json")
    data = r.json()["ticket"]

    # 2. Extrai texto limpo do HTML dos comentários
    elements = partition_html(text=data["description"])
    clean_text = "\n".join([str(e) for e in elements])

    # 3. Enriquece com metadados (crucial para busca!)
    return {
        "text": clean_text,
        "metadata": {
            "source": "zendesk",
            "ticket_id": ticket_id,
            "priority": data["priority"],
            "status": data["status"],
            "created_at": data["created_at"],
            "tags": data["tags"]
        }
    }

Ferramentas do mercado

FerramentaUso idealPreço
Unstructured.ioParser universal (PDF, HTML, DOCX)Open-source / Cloud
LlamaParsePDFs complexos com tabelasFreemium
PyMuPDF (fitz)PDFs nativos, rápido e leveOpen-source
Docling (IBM)Acadêmico/científico, preserva estruturaOpen-source
⚠️ Regra de ouro: "Garbage in, garbage out". Se você indexar texto sujo, com cabeçalhos repetidos e tabelas quebradas, seu RAG vai responder mal — não importa quão bom seja o modelo.

4 Chunking — cortando em pedaços

Dividir para conquistar — mas onde cortar faz toda diferença.

💡 Analogia
🍕Chunking é como cortar uma pizza. Pedaços muito grandes: difíceis de comer. Pedaços muito pequenos: perde o recheio. Overlap é como deixar um pouquinho de recheio em cada fatia adjacente.

🎮 Simulador de Chunking

Ajuste os parâmetros e veja o resultado em tempo real

180 tok
30 tok

Cores diferentes = chunks diferentes. Regiões com fundo colorido = overlap (palavras repetidas entre chunks adjacentes).

Estratégia por tipo de documento

TipoEstratégiaTamanhoOverlap
Ticket de suportePor comentário/resposta200–300 tok20–40 tok
Documentação APIPor endpoint/seção300–500 tok40–80 tok
RunbookPor passo do procedimento250–400 tok30–60 tok

Enriquecendo chunks com metadados

Prefixe cada chunk com informações do documento antes de indexar:

[Ticket #4521 | Acme Corp | Prioridade: Alta | API v3.2 | 2026-06-20]
Cliente reporta falhas intermitentes ao sincronizar arquivos
maiores que 500MB. Logs mostram timeout no endpoint /v2/sync
após exatamente 30 segundos.
Solução: aumentar NIMBUS_TIMEOUT para 120s.

5 Embeddings — o GPS do significado

Como um modelo comprime o significado de um texto em um vetor.

💡 Analogia
🗺️Imagine um mapa mundial de conceitos. Cada texto é um ponto nesse mapa. "Rei" e "rainha" ficam pertinho. "Rei" e "maçã" ficam em continentes diferentes.

Na prática: scores de similaridade

"timeout na sincronização"
0.94
"falha após 30s no /v2/sync"
0.88
"lentidão no upload"
0.65
"receita de bolo"
0.12

A pergunta "por que minha sincronização falha?" é transformada em vetor e comparada com todos os chunks. Os mais próximos semanticamente ganham score alto — mesmo sem compartilhar palavras exatas.

Modelos populares em 2026

ModeloDimTamanhoUso
nomic-embed-text768274 MBRápido, local (Ollama)
mxbai-embed-large1024670 MBTécnico/jurídico
text-embedding-3-large (OpenAI)3072APIMá qualidade, pago
jina-embeddings-v31024~600 MBMultilíngue, MTEB top
⚠️ Pegadinha clássica: se você indexar com nomic-embed-text e consultar com mxbai-embed-large, a busca falha completamente. Os vetores vivem em "espaços" incompatíveis. Sempre use o mesmo modelo nas duas fases.

6 A matemática por trás

Cosine similarity, HNSW e por que a busca é rápida mesmo com milhões de vetores.

💡 Analogia
🧭Imagine duas setas em uma bússola. Se apontam para a mesma direção, o ângulo entre elas é 0° — similaridade 1.0. Se apontam para lados opostos, 180° — similaridade -1.0.

Cosine Similarity — a régua do RAG

import numpy as np

def cosine_similarity(a, b):
    """Calcula similaridade cosseno entre dois vetores."""
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

# Exemplo: pergunta vs. chunk
pergunta = np.array([0.19, -0.68, 0.51, 0.09])
chunk_1  = np.array([0.23, -0.71, 0.45, 0.08])  # timeout sync
chunk_2  = np.array([-0.82, 0.34, -0.19, 0.91]) # receita bolo

print(cosine_similarity(pergunta, chunk_1))  # 0.997 ✅
print(cosine_similarity(pergunta, chunk_2))  # -0.42 ❌

O problema: escala

Se você tem 1 milhão de chunks, comparar a pergunta com cada um (força bruta) custa 1 milhão de cosine similarities. Em vetores de 768 dimensões, isso é lento.

A solução: índices aproximados (ANN)

💡 Analogia do HNSW
🕸️Imagine uma rede de amigos em uma festa. Você quer achar alguém que gosta de "timeout em APIs". Em vez de perguntar para todos os 1000 convidados, você pergunta para 5 amigos próximos, que indicam outros 5, e assim por diante — em poucos "pulos" você chega na pessoa certa.
🕸️

HNSW

Cria um grafo em múltiplas camadas. A busca "pula" de nó em nó, aproximando-se progressivamente do alvo.

Complexidade: O(log n) — muito rápido. Padrão no ChromaDB/Qdrant.

📁

IVF

Divide o espaço em "clusters" (Voronoi cells). A busca só examina os clusters mais próximos do vetor de consulta.

Bom para datasets muito grandes (>10M vetores). Usado no Faiss.

7 Busca — vetorial, lexical e híbrida

Vetores não bastam — especialmente quando há códigos, números e IDs.

💡 Analogia
🔍Busca vetorial é como o Google (entende o que você quer). Busca lexical é como o Ctrl+F (acha a palavra exata). Busca híbrida é usar os dois ao mesmo tempo.

🎮 Simulador de Busca Híbrida

Ajuste os pesos e veja o ranking mudar

0.60
0.40

Reranking — o refinamento final

💡 Analogia
🎤A busca vetorial é a triagem de currículos (rápida, filtra 1000 → 20). O reranker é a entrevista final (cuidadosa, escolhe os 5 melhores).

Após recuperar os top-K chunks (ex: 20), um segundo modelo (cross-encoder) reordena os resultados avaliando a pergunta e cada chunk juntos. É mais caro, mas muito mais preciso.

# Pipeline completo de busca
1. Busca híbrida → top-20 chunks (rápido, aproximado)
2. Cross-encoder reranker → reordena os 20 (lento, preciso)
3. Pega top-5 → envia ao LLM como contexto

8 Estratégias avançadas

Quando o chunking básico não é suficiente.

🪆 Parent-Child Retrieval (Small-to-Big)

💡 Analogia
🔬É como usar uma lupa para achar um parágrafo específico, mas depois ler a página inteira para entender o contexto.

Ideia: indexar chunks pequenos (precisão na busca) mas retornar o chunk pai (contexto completo para o LLM).

🧠 HyDE (Hypothetical Document Embeddings)

💡 Analogia
🎭É como pedir para alguém descrever o suspeito antes de mostrar fotos. A descrição ajuda a achar a foto certa mais rápido.

Problema: perguntas curtas geram embeddings "pobres". Solução: pedir ao LLM para gerar uma resposta fictícia antes de buscar.

🔗 GraphRAG

💡 Analogia
🕸️É como construir uma árvore genealógica dos documentos. Um ticket cita uma release note, que cita um bug, que cita outro ticket — o grafo captura essas relações.

🤖 Agentic RAG

💡 Analogia
🕵️É como um detetive que decide sozinho quando buscar mais pistas, o que buscar e se já tem informação suficiente para resolver o caso.

9 Engenharia de Prompt para RAG

O prompt é o "briefing" do estagiário. Mal feito, tudo desmorona.

💡 Analogia
📋Um prompt é como um briefing para um estagiário muito inteligente, mas muito literal. Se você não diz "não invente", ele inventa. Se não diz "cite a fonte", ele não cita.

Template de prompt para RAG

# SYSTEM
Você é o "Nimbus Assistant", ajudante de suporte técnico da Nimbus Cloud.

REGRAS:
1. Responda APENAS com base no CONTEXTO fornecido.
2. Se a informação não estiver no contexto, responda exatamente:
   "Não encontrei essa informação na base de conhecimento."
3. Cite a fonte de cada afirmação no formato [Fonte: XXX].
4. Se houver múltiplas fontes, liste todas.
5. Seja conciso e técnico.

CONTEXTO:
{contexto_formatado}

PERGUNTA DO USUÁRIO:
{pergunta}

Formatando o contexto com citações

def format_context(chunks):
    parts = []
    for i, c in enumerate(chunks, 1):
        src = c.metadata.get("ticket_id") or c.metadata.get("doc")
        parts.append(f"[Fonte {i}: {src}]\n{c.text}")
    return "\n\n---\n\n".join(parts)

10 Observabilidade e MLOps

Colocar em produção é fácil. Manter funcionando é que são elas.

🧠 Semantic Cache

💡 Analogia
💾É como um Ctrl+C / Ctrl+V inteligente. Se alguém fez a mesma pergunta ontem, por que gastar tokens do LLM de novo? Retorna a resposta do cache.

📊 Tracing — rastreie tudo

💡 Analogia
📹É como uma câmera de segurança na fábrica. Se algo deu errado, você volta o vídeo e vê em qual estação o problema começou.

Ferramentas: Langfuse (open-source), Arize Phoenix, LangSmith, Helicone.

📈 Avaliação contínua com RAGAS

💡 Analogia
📝É como um prova semanal para o seu RAG. Você dá 100 perguntas com respostas esperadas e mede quantas ele acerta — em cada dimensão.
MétricaO que medeIdeal
FaithfulnessA resposta é fiel ao contexto? (sem alucinação)> 0.9
Answer RelevancyA resposta responde à pergunta?> 0.85
Context PrecisionOs chunks certos foram recuperados?> 0.8
Context RecallToda informação necessária foi recuperada?> 0.8

11 Arquitetura e Deploy

Como empacotar tudo isso em algo que rode em produção.

Arquitetura recomendada

🌐 Frontend (React/Next.js)
⚡ API Gateway (FastAPI)
🔍 Retrieval Service
🤖 Generation Service
🗄️ ChromaDB / Qdrant
🧠 Ollama (LLM + Embed)
📊 Langfuse

API mínima em FastAPI

from fastapi import FastAPI
from pydantic import BaseModel
import chromadb
import ollama

app = FastAPI()
db = chromadb.HttpClient(host="localhost", port=8000)
collection = db.get_collection("nimbus_docs")

@app.post("/ask")
async def ask(q: Query):
    # 1. Embed da pergunta
    emb = ollama.embed(model="nomic-embed-text", input=q.question)

    # 2. Busca vetorial
    results = collection.query(
        query_embeddings=[emb["embeddings"[0]]],
        n_results=q.top_k
    )

    # 3. Monta prompt
    contexto = format_context(results)
    prompt = SYSTEM_PROMPT.format(contexto=contexto, pergunta=q.question)

    # 4. Gera resposta
    resp = ollama.chat(
        model="llama3.1:8b",
        messages=[{"role": "user", "content": prompt}]
    )

    return {
        "answer": resp["message"]["content"],
        "sources": results["metadatas"][0]
    }

🐳 Docker Compose para subir tudo

version: '3.8'
services:
  api:
    build: .
    ports: ["8080:8080"]
    depends_on: [chromadb, ollama]

  chromadb:
    image: chromadb/chroma:latest
    ports: ["8000:8000"]
    volumes: ["./chroma_data:/chroma/chroma"]

  ollama:
    image: ollama/ollama:latest
    ports: ["11434:11434"]

  langfuse:
    image: langfuse/langfuse:2
    ports: ["3000:3000"]

🎯 Quiz — teste seu conhecimento

Clique em uma alternativa para ver se acertou.

1. Por que o RAG é necessário mesmo com LLMs tão capazes?
LLMs não conseguem gerar texto longo o suficiente
LLMs só sabem o que foi treinado — não conhecem seus documentos específicos
RAG é mais rápido que LLMs para qualquer tipo de consulta
2. O que é um embedding?
Um modelo de linguagem que gera respostas longas
Uma técnica de compressão de arquivos PDF
Uma representação numérica (vetor) do significado de um texto
3. Por que existe overlap entre chunks?
Para evitar que informações importantes fiquem cortadas na junção entre dois chunks
Para reduzir o número total de chunks necessários
Para que o LLM possa ignorar chunks repetidos
4. Você precisa buscar "ticket #4521" exatamente. Qual estratégia usar?
Apenas busca vetorial — embeddings são suficientes
Busca híbrida (vetorial + BM25) — match exato requer busca lexical
Aumentar o tamanho dos chunks resolve
5. Ao trocar o modelo de embedding após a indexação, o que acontece?
Nada — o Vector DB converte automaticamente
As respostas ficam mais lentas mas continuam corretas
A busca por similaridade falha — os vetores ficam em "espaços" incompatíveis
6. O que é o Parent-Child Retrieval?
Indexar documentos pais e buscar apenas nos filhos
Indexar chunks pequenos (precisão) mas retornar o chunk pai (contexto rico)
Uma técnica para dividir documentos em duas cópias
7. O que o RAGAS mede?
Métricas de qualidade do pipeline: faithfulness, relevancy, precision
A velocidade de resposta do LLM
O custo em tokens de cada consulta
8. Qual algoritmo o ChromaDB usa para busca rápida em milhões de vetores?
Força bruta comparando com todos
Árvore binária de busca
HNSW (grafo hierárquico navegável)
9. O que é Parsing/ETL no contexto de RAG?
O processo de gerar respostas com o LLM
Transformar documentos brutos (PDF, HTML) em texto limpo e estruturado
Avaliar a qualidade das respostas do RAG
10. O que é reranking?
Reavaliar os chunks candidatos com um modelo mais preciso antes de enviar ao LLM
Repetir a busca vetorial duas vezes para ter mais resultados
Reescrever a pergunta do usuário antes de buscar

O que vem a seguir?

Agora que você construiu um RAG completo, vamos dar ao seu sistema a capacidade de agir no mundo real.

🤖
Próximo módulo
Módulo 8 · Agents e Tool Use
LLMs que agem no mundo real: function calling, ReAct, multi-agent systems, agentic RAG. O modelo decide quando buscar, o que buscar e se precisa buscar mais.

Conceitos que vamos construir aqui

🔧

Function Calling

LLMs executando código e chamando APIs.

🎭

ReAct

Raciocinar + Agir em loop.

👥

Multi-Agent

Vários agentes colaborando (AutoGen, CrewAI).

🕵️

Agentic RAG

O agente decide quando e o que buscar.

💡 Conexão final: Agents são a evolução natural do RAG. Em vez de buscar uma vez e responder, o agente pode fazer múltiplas buscas, chamar APIs, executar código e decidir sozinho quando tem informação suficiente. É o próximo nível!